/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package tnviet.proj.jna;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Date;
import java.util.Vector;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import jpcap.JpcapCaptor;
import jpcap.JpcapWriter;
import jpcap.NetworkInterface;
import jpcap.PacketReceiver;
import jpcap.packet.EthernetPacket;
import jpcap.packet.Packet;
import tnviet.proj.jna.dialog.JNCaptureDialog;
import tnviet.proj.jna.stat.JNContinuousStatDialog;
import tnviet.proj.jna.stat.JNCumulativeStatDialog;
import tnviet.proj.jna.stat.JNStatDialog;
import tnviet.proj.jna.stat.JNStatisticsController;
import tnviet.proj.jna.utilities.ResourceManager;

/**
 * Singleton class.
 * A class to capture packets and handle receive packets event.
 *
 * @author Administrator
 */
public class JNCaptor {
    
    private JNCaptor(){
        packets = new Vector();
        statDialogs = new Vector();
        devices = JpcapCaptor.getDeviceList();
        deviceLength = devices.length;
        packetsInCount = 0;
        packetsOutCount = 0;
        trafficMeterThread = new Thread[deviceLength];
        localMAcs = new byte[deviceLength][];
        jpcapMeter = new JpcapCaptor[deviceLength];
        trafficHandler = new PacketReceiver[deviceLength];
        bytesIn = new Double(0.0d);
        bytesOut = new Double(0.0d);
        for(int j=0; j<deviceLength; j++){
            
            final int i = j;
            localMAcs[j] = devices[j].mac_address;
            trafficHandler[i] = new PacketReceiver() {
                public void receivePacket(Packet packet) {
                    if(packet.datalink!= null && packet.datalink instanceof EthernetPacket){
                        if(Arrays.equals(localMAcs[i],((EthernetPacket)packet.datalink).src_mac)){
                            synchronized(lock1){
                                bytesOut = bytesOut + packet.len;
                                packetsOutCount++;
                            }
                        } else if(Arrays.equals(localMAcs[i], ((EthernetPacket)packet.datalink).dst_mac)){
                            synchronized(lock2){
                                bytesIn = bytesIn + packet.len;
                                packetsInCount++;
                            }
                                
                        }
                    }
                }
            };
        }
        this.StartTrafficMeter();
    }
    public void ResetCounter(){
        synchronized(lock1){
            bytesOut = 0.0d;
            packetsOutCount = 0;
        }
        synchronized(lock2){
            bytesIn = 0.0d;
            packetsInCount= 0;
        }
    }

    public static JNCaptor getInstance(){
        return INSTANCE;
    }
    private static JNCaptor INSTANCE = new JNCaptor();

    // Constant
    long DEFAULT_MAX_PACKETS_HOLD = 100000;
    long maxPacketsHold = DEFAULT_MAX_PACKETS_HOLD;

    private ResourceManager resourceManager = new ResourceManager(this.getClass(), Program.locale);

    // Fields
    public NetworkInterface[] devices;
    final int deviceLength;
    public Double bytesIn = new Double(0.0d);
    public Double bytesOut = new Double(0.0d);
    public long packetsInCount, packetsOutCount;
    Vector packets;
    JpcapCaptor jpcap = null;
    boolean isLiveCapture = false;
    boolean isSaved = true;
    JNMainFrame mainFrame;
    Thread trafficMeterThread[];
    Thread captureThread;
    byte[][] localMAcs;
    JpcapCaptor[] jpcapMeter;
    PacketReceiver[] trafficHandler;
    Vector statDialogs;
    long alarmPacketsCount = 0, alarmTimeCount = 0;
    long stopTime = 0;
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();

    public Vector getPackets(){
        return packets;
    }

    public void setJNetMainFrame(JNMainFrame frame){
        this.mainFrame = frame;
    }

    public void setMaxPacketsHold(long max){
        maxPacketsHold = max;
    }

    /**
     * Start capture all packets of the computer's network interfaces.
     * Just capture 68 bytes, minium to the header and information.
     */
    public void StartTrafficMeter(){
        for(int i=0; i< devices.length; i++){
            try {
                final int j = i;
                jpcapMeter[j] = JpcapCaptor.openDevice(devices[j], 68, false, 10);
                startTrafficMeterThread(j);
            } catch (IOException ex) {
            }
        }
    }

    /**
     * Start a capture thread just to count in/out traffic.
     * @param i index of the network interface need to monitor.
     */
    public void startTrafficMeterThread(final int i){
        if(trafficMeterThread[i] != null){
            return;
        }
        trafficMeterThread[i] = new Thread(new Runnable() {

            public void run() {
                while(trafficMeterThread[i] != null){
                    try {
                        jpcapMeter[i].processPacket(1, trafficHandler[i]);
                    } catch (Exception e) {
                    }
                    
                    Thread.yield();
                }
                jpcapMeter[i].breakLoop();
            }
        });
        trafficMeterThread[i].setPriority(Thread.MIN_PRIORITY);
        trafficMeterThread[i].start();
    }

    public int saveIfNot(){
        if((isLiveCapture || !isSaved)&& packets.size()> 0){
            int ret = JOptionPane.showConfirmDialog(
                    mainFrame, resourceManager.getString("saveIfNot"),
                    resourceManager.getString("comfirmSave"),
                    JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
            if(ret == JOptionPane.CANCEL_OPTION){
                return ret;
            } else if(ret == JOptionPane.YES_OPTION){
                this.SavePacketsToFile();
                Program.isCaptureFrameActived = Boolean.FALSE;
                return ret;
            } else if(ret == JOptionPane.NO_OPTION){
                Program.isCaptureFrameActived = Boolean.FALSE;
                return ret;
            }
        }
        return JOptionPane.YES_OPTION;
    }

    public void clear(){
        this.packets.clear();
        mainFrame.clear();
        alarmPacketsCount = 0;
        alarmTimeCount = 0;
        for(int i=0; i<statDialogs.size();i++){
            ((JNStatDialog)statDialogs.elementAt(i)).clear();
        }
    }

    public void startCaptureThread(){
        if(captureThread != null){
            return;
        } else {
            if(alarmPacketsCount==0 && alarmTimeCount==0){
                captureThread = new Thread(new Runnable() {
                public void run() {
                    while(captureThread != null){
                        if(jpcap.processPacket(1, manualHandler) == 0 && !isLiveCapture){
                            stopCaptureThread();
                        }
                        Thread.yield();
                    }
                    jpcap.breakLoop();
                }
                });
            } else {
                stopTime = new Date().getTime() + alarmTimeCount;
                captureThread = new Thread(new Runnable() {
                public void run() {
                    while(captureThread != null){
                        if(jpcap.processPacket(1, manualHandlerWithAlarm) == 0 && !isLiveCapture){
                            stopCaptureThread();
                        }
                        Thread.yield();
                    }
                    jpcap.breakLoop();
                }
                });
            }
            
            captureThread.setPriority(Thread.MIN_PRIORITY);
            mainFrame.startUpdating();
            for(int i=0; i<statDialogs.size();i++){
                ((JNStatDialog)statDialogs.elementAt(i)).startUpdating();
            }
            captureThread.start();
        }
    }
    public void RestartCaptureFromDevice(){
        if(jpcap!=null){
            clear();
            isLiveCapture=true;
            startCaptureThread();
        }
    }

    public void stopCaptureThread(){
        captureThread = null;
        mainFrame.stopUpdating();
        isLiveCapture = false;
    }

    public void CapturePacketsFromDevice(){
        JNCaptureDialog dialog = new JNCaptureDialog(mainFrame);
        dialog.setVisible(true);
        int option = dialog.getOption();
        if(option == JOptionPane.OK_OPTION){
            this.jpcap = dialog.getJpcap();
            if(jpcap != null){
                clear();
                isLiveCapture = true;
                this.alarmPacketsCount = dialog.getAlarmPacketsCount();
                this.alarmTimeCount = dialog.getAlarmTimeCount();
                startCaptureThread();
            }
        }
        dialog.dispose();
    }

    public void LoadPacketsFromFile(){
        saveIfNot();
        isLiveCapture = false;
        clear();

        int ret = JNMainFrame.chooser.showOpenDialog(mainFrame);
        if(ret == JFileChooser.APPROVE_OPTION){
            String path = JNMainFrame.chooser.getSelectedFile().getPath();
            String fileName= JNMainFrame.chooser.getSelectedFile().getName();
            String pathDirectory = JNMainFrame.chooser.getSelectedFile().getParentFile().getPath();
            System.out.println(path);
            System.out.println(fileName);
            System.out.println(pathDirectory);
            try {
                this.jpcap = JpcapCaptor.openFile(path);
                if(jpcap != null){
                    Program.JNProperty.setProperty("lastDir", pathDirectory);
                    Program.JNProperty.setProperty("lastFile", path);
                    clear();
                    startCaptureThread();
                }
            } catch (Exception e) {
                JOptionPane.showMessageDialog(mainFrame,
                        resourceManager.getString("error.unableToOpenFile"),
                        resourceManager.getString("error.title"),
                        JOptionPane.ERROR_MESSAGE);
            }
            mainFrame.setStopEnabled(false);
            mainFrame.setStartEnabled(true);
            mainFrame.setRestartEnabled(false);
            mainFrame.setOpenEnabled(true);
            mainFrame.setSaveEnabled(false);
            mainFrame.setSaveAsEnabled(true);
            mainFrame.setCloseEnabled(true);
            mainFrame.setReloadEnabled(true);
        }
    }
    public void Reload(){
        String path = Program.JNProperty.getProperty("lastFile");
        File file = new File(path);
        if(file.exists()&&!path.equals("")){
            try {
                this.jpcap = JpcapCaptor.openFile(path);
                if(jpcap != null){
                    Program.JNProperty.setProperty("lastFile", path);
                    clear();
                    startCaptureThread();
                }
            } catch (Exception e) {
                JOptionPane.showMessageDialog(mainFrame,
                        resourceManager.getString("error.unableToOpenFile"),
                        resourceManager.getString("error.title"),
                        JOptionPane.ERROR_MESSAGE);
            }
            mainFrame.setStopEnabled(false);
            mainFrame.setStartEnabled(true);
            mainFrame.setRestartEnabled(false);
            mainFrame.setOpenEnabled(true);
            mainFrame.setSaveEnabled(false);
            mainFrame.setSaveAsEnabled(true);
            mainFrame.setCloseEnabled(true);
            mainFrame.setReloadEnabled(true);
        }
    }
    public void SavePacketsToFile(){
        if(packets == null||packets.size()==0){
            JOptionPane.showMessageDialog(mainFrame, resourceManager.getString("warning.noDataCapture"),
                        resourceManager.getString("warning"), JOptionPane.WARNING_MESSAGE);
            return;
        }
        int ret = JNMainFrame.chooser.showSaveDialog(mainFrame);
        if(ret == JFileChooser.APPROVE_OPTION){
            File file = JNMainFrame.chooser.getSelectedFile();
            String pathDirectory = file.getParentFile().getPath();
            Program.JNProperty.setProperty("lastDir", pathDirectory);
            if(file.exists()){
                if(JOptionPane.showConfirmDialog(
                        mainFrame,
                        resourceManager.getString("overwrite.confirm"),
                        resourceManager.getString("confirm"),
                        JOptionPane.YES_NO_OPTION,
                        JOptionPane.QUESTION_MESSAGE) == JOptionPane.NO_OPTION) return;
            }
            try {
                JpcapWriter writer = JpcapWriter.openDumpFile(jpcap,file.getPath());
                for (int i = 0; i < packets.size(); i++) {
                    writer.writePacket((Packet) packets.elementAt(i));
                }
                writer.close();
                isSaved = true;
                mainFrame.setSaveAsEnabled(false);
            } catch (Exception e) {
                JOptionPane.showMessageDialog(mainFrame, resourceManager.getString("error.unableToSaveFile"),
                        resourceManager.getString("error.title"), JOptionPane.ERROR_MESSAGE);
            }
        }
    }
    public void StopCaptureFromDevice(){
        stopCaptureThread();
        for(int i=0; i<statDialogs.size();i++){
            ((JNStatDialog)statDialogs.elementAt(i)).stopUpdating();
        }
    }
    private PacketReceiver manualHandler = new PacketReceiver() {

        public void receivePacket(Packet packet) {
            packets.addElement(packet);
            while(packets.size()>maxPacketsHold){
                packets.removeElementAt(0);
            }
            if(!statDialogs.isEmpty()){
                for(int i=0; i<statDialogs.size();i++){
                    ((JNStatDialog)statDialogs.elementAt(i)).addPacket(packet);
                }
            }
            isSaved = false;
        }
    };
    private PacketReceiver manualHandlerWithAlarm = new PacketReceiver() {
        public void receivePacket(Packet packet) {
            packets.addElement(packet);
            if(packets.size()==alarmPacketsCount && alarmPacketsCount>0){
                try {
                    SwingUtilities.invokeAndWait(new Runnable() {

                        public void run() {
                            mainFrame.stopButton.doClick();
                            JOptionPane.showMessageDialog(mainFrame, resourceManager.getString("alarm.maxPacket1") + packets.size() + " " + resourceManager.getString("alarm.maxPacket2"), resourceManager.getString("alarm.title"), JOptionPane.INFORMATION_MESSAGE);
                        }
                    });
                } catch (InterruptedException ex) {
                } catch (InvocationTargetException ex) {
                }
            }
            if(stopTime<new Date().getTime() && alarmTimeCount>0){
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        mainFrame.stopButton.doClick();
                        JOptionPane.showMessageDialog(mainFrame,
                                resourceManager.getString("alarm.maxPacket1") + packets.size() +" " + resourceManager.getString("alarm.maxPacket2"),
                                resourceManager.getString("alarm.title"), JOptionPane.INFORMATION_MESSAGE);
                    }
                });
            }
            if(!statDialogs.isEmpty()){
                for(int i=0; i<statDialogs.size();i++){
                    ((JNStatDialog)statDialogs.elementAt(i)).addPacket(packet);
                }
            }
            isSaved = false;
        }
    };
    public void addCumulativeStatDialog(JNStatisticsController controller){
        statDialogs.addElement(JNCumulativeStatDialog.createDialog(mainFrame, packets, controller.newInstance()));
    }
    public void addContinuousStatDialog(JNStatisticsController controller){
        statDialogs.addElement(JNContinuousStatDialog.createDialog(mainFrame, packets, controller.newInstance()));
    }
    public void clearAllStatDialog(){
        for(int i=0; i<statDialogs.size();i++){
            ((JNStatDialog)statDialogs.elementAt(i)).dispose();
        }
        statDialogs.removeAllElements();
    }
    public void removeStatDialog(Object obj){
        if(!statDialogs.isEmpty()){
            statDialogs.removeElement(obj);
        }
    }
}
